57 绘制条形图
本章定位(复习与补充训练层):本章对应正课第20章《常用图形绘制》(章节 20,“分类比较用柱状图/条形图”的选型与绘制),并与第49章《数据框可视化》(章节 50)互为补充训练(本章为分项版),用于复习、补缺与额外练习。建议先不看讲解,直接尝试下方平台任务,再对照解析补弱项。本章不属于必修主线。先做本章『动手与思考』第 1 题与平台任务自测,通过即可跳过本章。
57.1 引言分类数据的可视化
条形图(Bar Plot)是展示分类数据最直观的工具,金融分析中用于不同类别间的量值比较、Top N 排名展示、同一指标不同时期的对比以及构成分析。
57.2 本章学习目标
通过本章复习,你将能够:
- 区分
plt.bar(x, height)与plt.barh(y, width)的参数含义与适用场景,并根据类别名称的长短与数量选择纵向或横向 - 复述平台任务的数据流:用
np.array按行组织各期收盘价、pd.DataFrame(data=..., index=..., columns=...)生成数据框、index_data/index_data.shift(1)-1计算月度涨跌幅、dropna后用iloc取某一行画一组条形 - 说明分组条形图的位置偏移(
x±width)、堆叠条形图的bottom累加逻辑,以及百分比堆叠图的口径转换 - 用条形图展示杜邦分析三因子,并解释 \(\text{ROE}\) 的三因素分解
先修:章节 20、章节 10(第20章的柱状图/条形图绘制与第10章的DataFrame构造,本章平台任务的数据组织依赖二者)。
57.3 条形图 vs 直方图
| 特性 | 条形图 | 直方图 |
|---|---|---|
| 数据类型 | 分类变量 | 数值变量 |
| X轴 | 离散类别 | 连续区间 |
| 条形宽度 | 可调整,有间隔 | 固定,无间隔 |
| 排列顺序 | 任意(通常按值排序) | 固定(按区间边界) |
| 应用场景 | 比较、排名 | 分布形态 |
57.4 基础条形图
任务要求(归纳自块内任务注释与代码):用列表给出 5 个月末时点与 4 个指数名称,用 np.array 按行组织各月末收盘价,构造行=月份、列=指数的 DataFrame;用 index_data/index_data.shift(1)-1 计算月度涨跌幅,dropna 去掉首月后用 print 输出。请将代码原样输入教学平台(注释除外),判定以平台为准。
平台任务1(平台原始代码)
以下代码与教学平台任务要求完全一致:
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd # 导入Pandas数据分析库
import numpy as np # 导入NumPy数值计算库
date=["2019年4月","2019年5月","2019年6月","2019年7月","2019年8月"]#创建一个日期的列表
name=['标普500指数','富时100指数','恒生指数','上证综指']#创建一个指数名称的列表
# 创建NumPy数组closeprice
closeprice=np.array([[2945.83,7418.22,29699.11,3078.34],[2752.06,7161.71,26901.09,
2898.70],[2941.76,7425.63,28542.62,2978.88],[2980.38,7586.78,27777.75,2932.51], # 续行:closeprice=np.array(的数据项
[2926.46,7207.18,25724.73,2886.24]]) #创建收盘价的数据框
index_data = pd.DataFrame(data=closeprice,index=date,columns=name) #生成月末收盘价的数据框
index_return = index_data/index_data.shift(1)-1 #生成月度涨跌幅的数据框
index_return = index_return.dropna() #删除缺失值
print(index_return) # 输出收益率数据预期输出(本机实际运行结果,数据内联于代码中;以平台运行结果为准):
标普500指数 富时100指数 恒生指数 上证综指
2019年5月 -0.065778 -0.034578 -0.094212 -0.058356
2019年6月 0.068930 0.036852 0.061021 0.027661
2019年7月 0.013128 0.021702 -0.026797 -0.015566
2019年8月 -0.018092 -0.050034 -0.073909 -0.015778
注意:代码中的日期列表虽含 2019 年 4 月,但 4 月为基准期,shift(1) 后首行全 NaN 被 dropna 删除,输出只剩 5~8 月四行。
任务要求(归纳自块内任务注释与代码):沿用任务一的月度涨跌幅数据,在 2×2 子图中分别绘制 2019 年 5~8 月各月四个指数的涨跌幅柱状图(宽度 0.5,依次黄、青、蓝、蓝着色),子图间共用 X/Y 轴,设置轴标签、图例与网格,保存为 1.png。请将代码原样输入教学平台(注释除外),判定以平台为准。
平台任务2(平台原始代码)
以下代码与教学平台任务要求完全一致:
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd # 导入Pandas数据分析库
import numpy as np # 导入NumPy数值计算库
from pylab import mpl # 导入mpl模块
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
plt.rcParams['font.sans-serif']=['SimHei'] # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False # 正常显示负号
date=["2019年4月","2019年5月","2019年6月","2019年7月","2019年8月"]#创建一个日期的列表
name=['标普500指数','富时100指数','恒生指数','上证综指']#创建一个指数名称的列表
# 创建NumPy数组closeprice
closeprice=np.array([[2945.83,7418.22,29699.11,3078.34],[2752.06,7161.71,26901.09,
2898.70],[2941.76,7425.63,28542.62,2978.88],[2980.38,7586.78,27777.75,2932.51], # 续行:closeprice=np.array(的数据项
[2926.46,7207.18,25724.73,2886.24]]) #创建收盘价的数据框
index_data = pd.DataFrame(data=closeprice,index=date,columns=name) #生成月末收盘价的数据框
index_return = index_data/index_data.shift(1)-1 #生成月度涨跌幅的数据框
index_return = index_return.dropna() #删除缺失值
plt.figure(figsize=(11,10)) # 创建图形画布
plt.subplot(2,2,1) #第1行、第1列子图
# 绘制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[0],width=0.5,label=u"2019年5月涨跌幅",facecolor="y")
plt.xticks (fontsize=13) # 设置X轴刻度标签
plt.yticks(fontsize=13) # 设置Y轴刻度标签
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90) # 设置Y轴标签
plt.legend(loc=9,fontsize=13) #图列放置在中上位置
plt.grid(True) # 显示网格线
plt.subplot(2,2,2,sharex=plt.subplot(2,2,1),sharey=plt.subplot(2,2,1)) #与第一个子图个的x轴和Y轴相同
# 绘制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[1],width=0.5,label=u"2019年6月涨跌幅",facecolor="c")
plt.xticks (fontsize=13) # 设置X轴刻度标签
plt.yticks(fontsize=13) # 设置Y轴刻度标签
plt.legend(loc=8,fontsize=13) #图列放置在中下位置
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90) # 设置Y轴标签
plt.grid(True) # 显示网格线
plt.subplot(2,2,3,sharex=plt.subplot(2,2,1),sharey=plt.subplot(2,2,1)) # 选择子图位置
# 绘制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[2],width=0.5,label=u"2019年7月涨跌幅",facecolor="b")
plt.xticks (fontsize=13) # 设置X轴刻度标签
plt.yticks(fontsize=13) # 设置Y轴刻度标签
plt.legend(loc=9,fontsize=13) # 添加图例
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90) # 设置Y轴标签
plt.grid(True) # 显示网格线
plt.subplot(2,2,4,sharex=plt.subplot(2,2,1),sharey=plt.subplot(2,2,1)) # 选择子图位置
# 绘制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[3],width=0.5,label=u"2019年8月涨跌幅",facecolor="b")
plt.xticks (fontsize=13) # 设置X轴刻度标签
plt.yticks(fontsize=13) # 设置Y轴刻度标签
plt.legend(loc=9,fontsize=13) # 添加图例
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90) # 设置Y轴标签
plt.grid(True) # 显示网格线
plt.savefig("1.png") # 保存图形至文件预期输出(本机实际运行结果,数据内联于代码中;以平台运行结果为准):本块无控制台输出,生成文件 1.png——2×2 子图各画四根柱子(四个指数),5 月子图四根柱子全为负值(黄),6 月全为正值(青),7 月正负混合、8 月全为负值(蓝);各子图共用 X/Y 轴刻度,便于逐月横向对比。
任务要求(归纳自块内任务注释与代码):用 plt.barh 以横向条形图对比 2019 年 6 月与 7 月两个月各指数的涨跌幅(Y 轴为指数名称,两组条形各高 0.5),设置 X 轴标签”涨跌幅”、标题与图例(loc=0 自动选位),显示图形并保存为 2.png。请将代码原样输入教学平台(注释除外),判定以平台为准。
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
import pandas as pd # 导入Pandas数据分析库
import numpy as np # 导入NumPy数值计算库
from pylab import mpl # 导入mpl模块
plt.rcParams['font.sans-serif']=['SimHei'] # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False # 正常显示负号
date=["2019年4月","2019年5月","2019年6月","2019年7月","2019年8月"]#创建一个日期的列表
name=['标普500指数','富时100指数','恒生指数','上证综指']#创建一个指数名称的列表
# 创建NumPy数组closeprice
closeprice=np.array([[2945.83,7418.22,29699.11,3078.34],[2752.06,7161.71,26901.09,
2898.70],[2941.76,7425.63,28542.62,2978.88],[2980.38,7586.78,27777.75,2932.51], # 续行:closeprice=np.array(的数据项
[2926.46,7207.18,25724.73,2886.24]]) #创建收盘价的数据框
index_data = pd.DataFrame(data=closeprice,index=date,columns=name) #生成月末收盘价的数据框
index_return = index_data/index_data.shift(1)-1 #生成月度涨跌幅的数据框
index_return = index_return.dropna() #删除缺失值
plt.figure (figsize=(8,5)) # 创建图形画布
plt.barh(y=index_return.columns,width=index_return.iloc[1],height=0.5,label=u'2019年6月涨跌幅') # 绘制柱状图
plt.barh(y=index_return.columns,width=index_return.iloc[2],height=0.5,label=u'2019年7月涨跌幅') # 绘制柱状图
plt.xticks(fontsize=13) # 设置X轴刻度标签
plt.xlabel(u"涨跌幅",fontsize=13) # 设置X轴标签
plt.yticks (fontsize=13) # 设置Y轴刻度标签
plt.title(u"比较4个股指在2019年6月和7月的月度涨跌幅",fontsize=13) # 设置图表标题
plt.legend (loc=0,fontsize=13) # 添加图例
plt.grid(True) # 显示网格线
plt.show() # 显示图形
plt.savefig("2.png") # 保存图形至文件预期输出(本机实际运行结果,数据内联于代码中;以平台运行结果为准):本块无控制台输出,生成文件 2.png——四个指数各两根左右方向的条形,同月的两组条形以图例区分:6 月四根全部向右,最长的是标普500(+0.069);7 月恒生(-0.027)与上证(-0.016)两根向左、标普与富时两根向右,即恒生与上证由涨转跌;先画的 6 月组条形会被后画的 7 月组部分遮挡,这是同一起点上重复绘制横向条形的固有读图注意点。
关键参数解析:
- color:条形颜色,使用专业配色
- alpha:透明度,0.8使图表不显沉重
- edgecolor:条形边框,白色增强分隔感
- 数值标签:在每个条形顶部标注具体值
57.5 水平条形图
# =============================================================================
# 题目:水平条形图——股票收益率排名
# =============================================================================
# 本任务演示如何绘制水平条形图,特别适合类别名称较长的情况
# 应用场景:股票排名、基金业绩对比
# ==================== 创建股票数据 ====================
stocks = ['贵州茅台', '五粮液', '招商银行', '中国平安', '平安银行',
'工商银行', '建设银行', '农业银行', '中国银行', '交通银行']
returns = [0.05, 0.03, 0.02, 0.015, 0.018,
0.012, 0.011, 0.01, 0.009, 0.008]
df_stocks = pd.DataFrame({
'股票': stocks,
'收益率': returns
}).sort_values('收益率') # 按收益率升序排序(从低到高)
# ==================== 绘制水平条形图 ====================
plt.figure(figsize=(10, 8))
# plt.barh()绘制水平条形图(bar horizontal)
# df_stocks['股票']:Y轴数据(类别)
# df_stocks['收益率']:X轴数据(条形长度)
plt.barh(df_stocks['股票'], df_stocks['收益率'],
# 使用列表推导式根据收益率值选择颜色
# 收益率 > 3%:红色(突出显示高收益)
# 收益率 ≤ 3%:青色(普通收益)
color=['#E3120B' if x > 0.03 else '#008080' for x in df_stocks['收益率']],
alpha=0.8)
# ==================== 添加数值标签 ====================
# df_stocks.iterrows():遍历DataFrame的每一行
# i:索引(行的位置)
# idx:行索引(原始索引)
# row:行的数据(Series对象)
for i, (idx, row) in enumerate(df_stocks.iterrows()):
# plt.text()在图上添加文字
# row['收益率'] + 0.002:X坐标(条形右侧0.002个单位)
# i:Y坐标(第i个条形的Y位置)
# f'{row["收益率"]:.1%}':文字内容(百分比格式,保留1位小数)
# va='center':垂直对齐方式为居中
plt.text(row['收益率'] + 0.002, i, f'{row["收益率"]:.1%}',
va='center', fontsize=10)
# ==================== 设置图表装饰 ====================
plt.title('股票收益率排名', fontsize=16, fontweight='bold')
plt.xlabel('收益率', fontsize=12)
plt.ylabel('股票', fontsize=12)
# axis='x':只显示X轴网格线(垂直网格线)
plt.grid(axis='x', alpha=0.3)
plt.tight_layout()
plt.show()
# ==================== 输出统计信息 ====================
print('收益率统计:')
print(f'最高: {df_stocks["收益率"].max():.2%}')
print(f'最低: {df_stocks["收益率"].min():.2%}')
print(f'平均: {df_stocks["收益率"].mean():.2%}')水平 vs 垂直条形图:
| 场景 | 推荐类型 | 理由 |
|---|---|---|
| 类别名称较长 | 水平 | 避免标签重叠 |
| 类别数量多(>10) | 水平 | 更易阅读 |
| 时间序列 | 垂直 | 符合时间从左到右的习惯 |
| 数值比较 | 垂直 | 长度差异更直观 |
颜色编码技巧:
- 用不同颜色突出重要类别(如收益率>3%用红色)
- 保持配色简洁,避免使用过多颜色
- 参考《经济学人》等专业图表的配色
57.6 分组条形图
# =============================================================================
# 题目:分组条形图——多指标对比
# =============================================================================
# 本任务演示如何绘制分组条形图,在同一类别中对比多个指标
# 应用场景:年度财务指标对比、多维度绩效评估
# ==================== 创建多年度数据 ====================
years = ['2021', '2022', '2023']
revenue = [100, 120, 135] # 营业收入(亿元)
profit = [15, 18, 22] # 净利润(亿元)
margin = [15, 15, 16.3] # 净利率(%)
# ==================== 准备绘图数据 ====================
# np.arange(len(years))生成X轴位置
# [0, 1, 2]:三个年份的位置
x = np.arange(len(years))
# width:每个条形的宽度
# 0.25是一个经验值,太细不好看,太粗易重叠
width = 0.25
# ==================== 创建图形和轴对象 ====================
fig, ax = plt.subplots(figsize=(10, 6))
# ==================== 绘制分组条形 ====================
# 第一组:营业收入
# x - width:向左偏移一个宽度(第一组位置)
# revenue:条形高度
# width:条形宽度
# label='营业收入':图例标签
# color='#2E86AB':深蓝色
bars1 = ax.bar(x - width, revenue, width, label='营业收入',
color='#2E86AB', alpha=0.8)
# 第二组:净利润
# x:中心位置(不偏移)
bars2 = ax.bar(x, profit, width, label='净利润',
color='#E3120B', alpha=0.8)
# 第三组:净利率
# x + width:向右偏移一个宽度(第三组位置)
bars3 = ax.bar(x + width, margin, width, label='净利率(%)',
color='#008080', alpha=0.8)
# ==================== 添加数值标签 ====================
# 循环遍历三个条形组
for bars in [bars1, bars2, bars3]:
# 遍历组内的每个条形
for bar in bars:
# bar.get_height():获取条形的高度
height = bar.get_height()
# bar.get_x() + bar.get_width()/2.:计算条形中心X坐标
# height:Y坐标(条形顶部)
# f'{height:.1f}':文字内容(保留1位小数)
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{height:.1f}', ha='center', va='bottom', fontsize=9)
# ==================== 设置图表装饰 ====================
ax.set_xlabel('年份', fontsize=12)
ax.set_ylabel('金额(亿元)', fontsize=12)
ax.set_title('财务指标年度对比', fontsize=16, fontweight='bold')
# ax.set_xticks()设置X轴刻度位置
ax.set_xticks(x)
# ax.set_xticklabels()设置X轴刻度标签
ax.set_xticklabels(years)
ax.legend(fontsize=11)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
# ==================== 计算增长率 ====================
# 2021-2023营业收入增长率
revenue_growth = (revenue[2] - revenue[0]) / revenue[0] * 100
# 2021-2023净利润增长率
profit_growth = (profit[2] - profit[0]) / profit[0] * 100
print(f'2021-2023营业收入增长率: {revenue_growth:.1f}%')
print(f'2021-2023净利润增长率: {profit_growth:.1f}%')分组条形图的设计要点:
- 条形宽度:通常0.2-0.3,太细不好看,太粗易重叠
- 间距:组间间距≥组内间距
- 颜色:同一组用同一色系,不同组用对比色
- 图例:清晰标注每个系列
数学原理: 设 \(n\) 个年份, \(m\) 个指标,第 \(i\) 年第 \(j\) 个指标的条形位置:
\[ x_{ij} = i + (j - \frac{m+1}{2}) \times w \] 其中 \(w\) 是条形宽度。
57.7 堆叠条形图
# =============================================================================
# 题目:堆叠条形图——收入构成分析
# =============================================================================
# 本任务演示如何绘制堆叠条形图,展示整体及各部分的构成
# 应用场景:收入构成、成本结构、投资组合配置
# ==================== 创建收入构成数据 ====================
years = ['2021', '2022', '2023']
primary_business = [80, 90, 100] # 主营业务收入(亿元)
other_business = [15, 20, 25] # 其他业务收入(亿元)
investment_income = [5, 10, 10] # 投资收益(亿元)
# ==================== 创建图形和轴对象 ====================
fig, ax = plt.subplots(figsize=(10, 6))
# ==================== 绘制堆叠条形图 ====================
# 第一层:主营业务收入(底层)
ax.bar(years, primary_business, label='主营业务收入',
color='#2E86AB', alpha=0.8)
# 第二层:其他业务收入(堆叠在第一层之上)
# bottom=primary_business:起始高度为第一层的顶部
ax.bar(years, other_business, bottom=primary_business,
label='其他业务收入', color='#008080', alpha=0.8)
# 第三层:投资收益(堆叠在第二层之上)
# bottom=[p+o for p, o in zip(primary_business, other_business)]:
# 起始高度为前两层之和(使用列表推导式计算)
ax.bar(years, investment_income,
bottom=[p+o for p, o in zip(primary_business, other_business)],
label='投资收益', color='#F0A700', alpha=0.8)
# ==================== 添加总数值标签 ====================
for i, year in enumerate(years):
# total:该年份的总收入(三层之和)
total = primary_business[i] + other_business[i] + investment_income[i]
# ax.text()在图上添加文字
# i:X坐标(第i个条形)
# total + 5:Y坐标(条形顶部上方5个单位)
# f'{total}':文字内容(总收入值)
# ha='center':水平居中
# va='bottom':垂直底部对齐
# fontsize=11, fontweight='bold':字号11,加粗
ax.text(i, total + 5, f'{total}', ha='center', va='bottom',
fontsize=11, fontweight='bold')
# ==================== 设置图表装饰 ====================
ax.set_xlabel('年份', fontsize=12)
ax.set_ylabel('收入(亿元)', fontsize=12)
ax.set_title('收入构成变化', fontsize=16, fontweight='bold')
# loc='upper left':图例位置在左上角
ax.legend(fontsize=11, loc='upper left')
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
# ==================== 计算占比变化 ====================
print('各业务占比变化:')
for i, year in enumerate(years):
total = primary_business[i] + other_business[i] + investment_income[i]
# 计算各业务占比
primary_pct = primary_business[i] / total * 100
other_pct = other_business[i] / total * 100
invest_pct = investment_income[i] / total * 100
print(f'{year}: 主营{primary_pct:.1f}%, 其他{other_pct:.1f}%, 投资{invest_pct:.1f}%')堆叠条形图的应用场景:
- 构成分析:展示整体及各部分
- 变化追踪:观察构成的时间变化
- 对比分析:不同类别的构成差异
注意事项:
- 不适合比较各部分的大小(基线不统一)
- 适合展示各部分占总体的比例
- 考虑用百分比堆叠图使比例更清晰
57.8 百分比堆叠条形图
# =============================================================================
# 题目:百分比堆叠条形图——投资组合配置
# =============================================================================
# 本任务演示如何绘制百分比堆叠条形图,展示各部分的相对比例
# 应用场景:资产配置变化、市场份额分析
# ==================== 创建资产配置数据 ====================
years = ['2020', '2021', '2022', '2023', '2024']
stocks = [60, 65, 55, 50, 45] # 股票配置(万元)
bonds = [30, 25, 30, 35, 40] # 债券配置(万元)
cash = [10, 10, 15, 15, 15] # 现金配置(万元)
# ==================== 转换为百分比 ====================
# np.array()将列表转换为NumPy数组(便于向量运算)
total = np.array(stocks) + np.array(bonds) + np.array(cash)
# 计算百分比
stocks_pct = np.array(stocks) / total * 100
bonds_pct = np.array(bonds) / total * 100
cash_pct = np.array(cash) / total * 100
# ==================== 创建图形和轴对象 ====================
fig, ax = plt.subplots(figsize=(10, 6))
# ==================== 绘制百分比堆叠条形图 ====================
# 第一层:股票(底层)
ax.bar(years, stocks_pct, label='股票',
color='#E3120B', alpha=0.8)
# 第二层:债券(堆叠在股票之上)
# bottom=stocks_pct:起始高度为股票百分比
ax.bar(years, bonds_pct, bottom=stocks_pct,
label='债券', color='#008080', alpha=0.8)
# 第三层:现金(堆叠在债券之上)
# bottom=stocks_pct + bonds_pct:起始高度为股票+债券百分比
ax.bar(years, cash_pct, bottom=stocks_pct + bonds_pct,
label='现金', color='#F0A700', alpha=0.8)
# ==================== 添加百分比标签 ====================
for i, year in enumerate(years):
# 股票标签(在第一层中间)
ax.text(i, stocks_pct[i]/2, f'{stocks_pct[i]:.0f}%',
ha='center', va='center', color='white', fontweight='bold')
# 债券标签(在第二层中间)
ax.text(i, stocks_pct[i] + bonds_pct[i]/2, f'{bonds_pct[i]:.0f}%',
ha='center', va='center', color='white', fontweight='bold')
# 现金标签(在第三层中间)
ax.text(i, stocks_pct[i] + bonds_pct[i] + cash_pct[i]/2, f'{cash_pct[i]:.0f}%',
ha='center', va='center', color='white', fontweight='bold')
# ==================== 设置图表装饰 ====================
ax.set_xlabel('年份', fontsize=12)
ax.set_ylabel('配置比例(%)', fontsize=12)
ax.set_title('投资组合配置变化', fontsize=16, fontweight='bold')
# ax.set_ylim(0, 100):设置Y轴范围为0-100%
ax.set_ylim(0, 100)
# loc='upper right':图例位置在右上角
# bbox_to_anchor=(1.15, 1):将图例放置在图表外侧(右侧)
ax.legend(fontsize=11, loc='upper right', bbox_to_anchor=(1.15, 1))
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
# ==================== 分析配置策略变化 ====================
print('配置策略变化:')
print(f'股票配置: 从{stocks_pct[0]:.0f}%降至{stocks_pct[-1]:.0f}%')
print(f'债券配置: 从{bonds_pct[0]:.0f}%升至{bonds_pct[-1]:.0f}%')
print('策略: 逐步从激进型转向稳健型')57.9 金融应用杜邦分析
# =============================================================================
# 题目:杜邦分析——ROE分解
# =============================================================================
# 本任务演示如何使用条形图展示杜邦分析的三个因子
# 应用场景:财务分析、ROE分解、公司对比
# ==================== 杜邦分析数据 ====================
# 杜邦分析公式:ROE = 销售净利率 × 总资产周转率 × 权益乘数
companies = ['公司A', '公司B', '公司C', '公司D', '公司E']
# ROE分解的三个因子
net_margin = [15, 10, 8, 20, 12] # 销售净利率(%)
asset_turnover = [1.2, 1.5, 0.8, 1.0, 1.3] # 总资产周转率
equity_multiplier = [2.0, 1.8, 2.5, 1.5, 2.2] # 权益乘数
# 计算ROE(净资产收益率)
# ROE = 销售净利率 × 总资产周转率 × 权益乘数 / 100(转换为百分比)
# 使用列表推导式计算每个公司的ROE
roe = [nm * at * em / 100 for nm, at, em in zip(net_margin, asset_turnover, equity_multiplier)]
# ==================== 创建双子图布局 ====================
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
# ==================== 左图:ROE对比 ====================
# axes[0].bar()在第一个子图绘制ROE
axes[0].bar(companies, roe, color='#2E86AB', alpha=0.8)
axes[0].set_title('ROE对比', fontsize=14, fontweight='bold')
axes[0].set_ylabel('ROE (%)', fontsize=12)
# axes[0].set_ylim(0, max(roe) * 1.2):设置Y轴范围为0到最大值的1.2倍
axes[0].set_ylim(0, max(roe) * 1.2)
axes[0].grid(axis='y', alpha=0.3)
# 添加ROE数值标签
for i, v in enumerate(roe):
axes[0].text(i, v + 1, f'{v:.1f}%', ha='center', va='bottom')
# ==================== 右图:杜邦三因子 ====================
# 准备X轴位置
x = np.arange(len(companies))
# 条形宽度
width = 0.25
# 绘制三个因子的分组条形图
# 第一组:销售净利率
axes[1].bar(x - width, net_margin, width, label='销售净利率(%)',
color='#E3120B', alpha=0.8)
# 第二组:总资产周转率(×10,使量级与其他因子相当)
axes[1].bar(x, [at * 10 for at in asset_turnover], width,
label='总资产周转率(x10)', color='#008080', alpha=0.8)
# 第三组:权益乘数(×5,使量级与其他因子相当)
axes[1].bar(x + width, [em * 5 for em in equity_multiplier], width,
label='权益乘数(x5)', color='#F0A700', alpha=0.8)
axes[1].set_title('杜邦三因子', fontsize=14, fontweight='bold')
axes[1].set_xticks(x)
axes[1].set_xticklabels(companies)
axes[1].legend(fontsize=10)
axes[1].grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
# ==================== 输出杜邦分析表格 ====================
print('杜邦分析:')
# f'{string:^width}':格式化输出,居中对齐
print(f'{"公司":^8s} {"ROE":^8s} {"净利率":^10s} {"周转率":^10s} {"权益乘数":^10s}')
print('-' * 50)
for i, company in enumerate(companies):
print(f'{company:^8s} {roe[i]:^8.1f} {net_margin[i]:^10.1f} {asset_turnover[i]:^10.2f} {equity_multiplier[i]:^10.2f}')杜邦分析的金融理论:
\[ \text{ROE} = \frac{\text{净利润}}{\text{股东权益}} = \frac{\text{净利润}}{\text{销售收入}} \times \frac{\text{销售收入}}{\text{总资产}} \times \frac{\text{总资产}}{\text{股东权益}} \]
三因子含义:
- 销售净利率:盈利能力,每1元销售带来的净利润
- 总资产周转率:效率,每1元资产创造的销售收入
- 权益乘数:杠杆,资产与权益的比率
分析要点:
- 高ROE可能来自高利润率(如白酒)
- 或高周转率(如零售)
- 或高杠杆(如银行、地产)
- 需结合行业特点综合分析
57.10 本章小结
要点:
- 条形图比较分类数值:纵向
plt.bar(x, height, width=...)适合时间顺序与少量类别,横向plt.barh(y, width)适合长标签与多类别排名 - 平台任务的数据链:月末收盘价二维数组 →
pd.DataFrame(data=closeprice, index=date, columns=name)(行=月份、列=指数) →df/df.shift(1)-1得月度涨跌幅 →dropna去掉首月 →iloc[i]取第i个月的四个指数值作为一组条形高度 - 分组条形图靠位置偏移实现:第 \(j\) 组条形中心在 \(i+(j-\frac{m+1}{2})w\),组内靠拢、组间留白,再配合
set_xticks(x)与set_xticklabels(...) - 堆叠条形图靠
bottom累加:后一层从前一层的顶部画起,用于展示构成;百分比堆叠图先把各层除以总数再堆叠 - 杜邦分解 \(\text{ROE}=\text{销售净利率}\times\text{总资产周转率}\times\text{权益乘数}\),三因子量纲不同,同图比较时需缩放或分面板展示
易错点:
np.array中按行给出的收盘价必须与index(月份)对应、按列与columns(指数)对应,行列颠倒会把”各期涨跌幅对比”画错成别的口径df/df.shift(1)-1与np.log(df/df.shift(1))分别是简单收益率与对数收益率,涨跌幅的口径要与题面一致- 平台任务1中数组的跨行书写是题面原样,复制到平台时不要破坏续行结构,判定以平台为准
plt.xticks的标签数量必须与刻度位置数量一致,否则报ValueErrorbarh画横向条形图后,网格方向应改为axis='x',沿用axis='y'会出现无效网格- 数值标注
plt.text的偏移量是绝对量,数据量级变化后需同步调整,否则标注会与柱顶重叠或飞出图外
57.11 动手与思考
以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。
自测回忆:不看正文,复述条形图与直方图在数据类型、X轴含义、条形间距、排列顺序四个方面的差异;并回答类别名称很长时应在
bar与barh之间选哪一个、为什么。参考答案(点开前请先独立完成)
解题思路:四个方面的差异——数据类型:条形图比较分类变量的汇总值(均值、占比等),直方图展示数值变量的分布;X 轴含义:条形图的 X 轴是类别(无序、可按数值重排),直方图的 X 轴是连续的数值区间(有序、条形按区间自然排列);条形间距:条形图的条形之间有间隔(类别离散),直方图的条形相连(区间衔接);排列顺序:条形图常按数值降序排列便于读排名,直方图必须按区间升序排列。类别名称很长时选
barh(水平条形图):长标签放在 Y 轴左侧有充足的横向书写空间,条形长短仍沿水平方向可比;纵向bar的长标签要么倾斜挤占画幅、要么换行重叠。回扣主线:“条形图用于分类比较、直方图用于数值分布”的分工见本章”条形图 vs 直方图”一节与第 章节 20 章选型要点。
输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。
import pandas as pd df = pd.DataFrame({'A': [10.0, 11.0, 12.1], 'B': [20.0, 22.0, 24.2]}) ret = df / df.shift(1) - 1 print(ret.iloc[0].tolist()) print(ret.dropna().shape) print(round(ret['A'].iloc[-1], 4))参考答案(先写下你的预测再点开)
解题思路:
df.shift(1)整体下移一行,首行变 NaN,NaN - 1与df/NaN仍是 NaN,故首行收益率为[nan, nan];dropna()删除首行后剩 2 行 2 列,即 (2, 2);A 列最后一个收益率是 12.1/11 − 1 = 0.1(12.1 恰为 11 的 1.1 倍),保留 4 位小数为 0.1。# 验证脚本:涨跌幅计算链的三个关键量 import pandas as pd # 导入pandas库 df = pd.DataFrame({'A': [10.0, 11.0, 12.1], 'B': [20.0, 22.0, 24.2]}) # 两列月末价格 ret = df / df.shift(1) - 1 # 简单收益率(涨跌幅) print(ret.iloc[0].tolist()) # 首行因shift(1)为NaN print(ret.dropna().shape) # 删除首行后的形状 print(round(ret['A'].iloc[-1], 4)) # A列最后一期收益率预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
[nan, nan] (2, 2) 0.1回扣主线:
df/df.shift(1)-1的数据链见本章”要点”第 2 条,与平台任务一致。输出预测:分组条形图的位置由偏移决定,先预测输出,再上机检验。
import numpy as np x = np.arange(3) width = 0.25 print((x - width).round(2)) print((x + width).round(2))参考答案(先写下你的预测再点开)
解题思路:
x = [0, 1, 2],向左、向右各平移一个条宽 0.25——x - width为 [−0.25, 0.75, 1.75],x + width为 [0.25, 1.25, 2.25]。四个系列以 0.25 为步长在整数位置两侧对称排开,组内靠拢、组间留白。# 验证脚本:条形中心的左右偏移 import numpy as np # 导入NumPy库 x = np.arange(3) # 三个类别的基准位置 width = 0.25 # 单根条形的宽度(兼作偏移步长) print((x - width).round(2)) # 左移一个条宽后的中心 print((x + width).round(2)) # 右移一个条宽后的中心预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
[-0.25 0.75 1.75] [0.25 1.25 2.25]回扣主线:分组条形图的位置偏移公式见本章”分组条形图”一节”要点”第 3 条。
变式任务(平台任务同型改造):平台任务2把四个月份的涨跌幅分画在2×2子图中;请改为一张图上的分组条形图——X轴为四个指数,每个月份一组条形(用
x + k*width偏移),图例区分月份,并对比两种形式各自的读图优势。参考答案(点开前请先独立完成)
解题思路:按平台任务同款数据(4 个月 × 4 个指数的月末收盘价)先算涨跌幅,再把每个月份的一组条形以
x + (k - 1.5) * width偏移画到同一坐标轴上。读图优势对比:2×2 分面把每个月份单独成图,月内四个指数的高矮对比最清晰,但跨月比较要在两图之间往返;分组条形图把同一个月的四根条形靠在一起,同一指数跨月的升降走势(如恒生指数 5 月 −9.42% 到 6 月 +6.10% 的大幅反弹)与同月内的横比一图完成,代价是类别多时组内条形偏密、单根条形变窄。# 变式脚本:四月份涨跌幅的分组条形图 import matplotlib.pyplot as plt # 导入绘图库 import pandas as pd # 导入pandas库 import numpy as np # 导入NumPy库 date = ['2019年4月', '2019年5月', '2019年6月', '2019年7月', '2019年8月'] # 月份列表 name = ['标普500指数', '富时100指数', '恒生指数', '上证综指'] # 指数列表 closeprice = np.array([[2945.83,7418.22,29699.11,3078.34],[2752.06,7161.71,26901.09,2898.70],[2941.76,7425.63,28542.62,2978.88],[2980.38,7586.78,27777.75,2932.51],[2926.46,7207.18,25724.73,2886.24]]) # 月末收盘价 index_data = pd.DataFrame(closeprice, index=date, columns=name) # 月末收盘价数据框 index_return = (index_data / index_data.shift(1) - 1).dropna() # 月度涨跌幅 print(index_return.round(4)) # 先输出涨跌幅表 x = np.arange(4) # 四个指数的基准位置 width = 0.2 # 条宽兼偏移步长 for k in range(4): # 依次画四个月份 plt.bar(x + (k - 1.5) * width, index_return.iloc[k], width, label=date[k + 1]) # 位置偏移+图例 plt.xticks(x, name) # 刻度位置与指数名对齐 plt.axhline(0, color='k', lw=0.8) # 画零基准线 plt.legend() # 显示图例 plt.savefig('grouped_bar.png') # 保存图形预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
标普500指数 富时100指数 恒生指数 上证综指 2019年5月 -0.0658 -0.0346 -0.0942 -0.0584 2019年6月 0.0689 0.0369 0.0610 0.0277 2019年7月 0.0131 0.0217 -0.0268 -0.0156 2019年8月 -0.0181 -0.0500 -0.0739 -0.0158图形:四个指数位置各有一组四根条形(黄、青、蓝、红区分月份),正负条形分居零线两侧,恒生指数 5 月的长负条与 6 月的正条对比最醒目。
注意:以上为变式代码;列表 57.2 的平台任务仍须按原始代码原样输入教学平台。
回扣主线:分组条形图的偏移公式与
set_xticks配合见本章”分组条形图”一节”要点”第 3 条。思考题:堆叠条形图为什么不适合比较”各部分的大小”?请从每个条形段的起点(基线)出发解释,并给出两种更适合的替代图形。
参考答案(点开前请先独立完成)
解题思路:条形长度比较依赖”共同基线”——人眼对从同一起点量起的长度差敏感,对起点错位的段长几乎不敏感。堆叠条形图中只有最底层的段从零基线起画,其上每一层的起点都被前面的层抬高到了各自不同的高度,于是第 2、3 层的段虽然可以逐段读出长度,却缺少共同参照,跨条形比较各部分大小时视觉判断严重失真;当各条总量接近时问题更突出。两种更适合的替代图形:分组条形图——每个部分一根独立条形、全部从零基线起跳,长短直接可比;按数值降序排列的(水平)条形图——把”各部分的大小”变成同基线下的排名阅读,类别名长时用
barh更佳。回扣主线:“堆叠图看构成、分组图比大小”的分工见本章”堆叠条形图”一节与第 章节 20 章。